package sundun.sharding.execution.impl;


import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sundun.sharding.config.TableConfiguration;
import sundun.sharding.execution.ExecutionStrategy;
import sundun.sharding.factory.SqlSessionFactoryWrapper;

import java.lang.reflect.*;
import java.sql.*;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;

/**
 * @author: zxl
 * @date: 2024/8/11 10:20
 */
public abstract class AbstractExecutionStrategy implements ExecutionStrategy {

    private static final Logger logger = LoggerFactory.getLogger(AbstractExecutionStrategy.class);

    private static final ThreadLocal<SqlSession> sqlSessionThreadLocal = new ThreadLocal<>();

    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private SqlSessionFactoryWrapper sqlSessionFactoryWrapper;
    private Map<String, TableConfiguration> tableConfigurations;
    private static Map<String, Method> executionMethod = new ConcurrentHashMap<>();


    public AbstractExecutionStrategy(SqlSessionFactoryWrapper sqlSessionFactoryWrapper, Map<String, TableConfiguration> tableConfigurations) {
        this.sqlSessionFactoryWrapper = sqlSessionFactoryWrapper;
        this.tableConfigurations = tableConfigurations;
    }


    @Override
    public Object execute(Invocation invocation,TableConfiguration tableConfiguration,MetaObject metaStatementHandler, MappedStatement mappedStatement) throws Exception {
        Object result = doExecute(invocation,tableConfiguration,metaStatementHandler,mappedStatement);
        return result;
    }

    protected Object invoke(Invocation invocation,MetaObject metaStatementHandler, MappedStatement mappedStatement) throws NoSuchMethodException, SQLException {

//        SqlSession sqlSession = sqlSessionFactoryWrapper.getSqlSessionFactory().openSession();
        SqlSession sqlSession = null;
        Connection conn = null;
        Object result = null;
        try{
            sqlSession = getSqlSession(); // 使用 ThreadLocal 获取 SqlSession
            Configuration configuration = mappedStatement.getConfiguration();
            BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
            String sql = getSql(configuration, boundSql);
            // 连接到老库
            // 获取一次连接并在循环外保持打开状态
            conn = sqlSession.getConnection();
            // 关闭自动提交，以便手动控制事务
            conn.setAutoCommit(false);
            try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                // 执行 SQL
                if (sql.toLowerCase().startsWith("select")) {
                    Class<?> resultType = getResultType(mappedStatement);
                    List<Object> results = new ArrayList<>();
                    try (ResultSet rs = pstmt.executeQuery()) {
                        // 处理查询结果
                        ResultSetMetaData metaData = rs.getMetaData();
                        int columnCount = metaData.getColumnCount();
                        while (rs.next()) {
                            //解析结果集
                            if (HashMap.class.isAssignableFrom(resultType) || Map.class.isAssignableFrom(resultType)){
                                Map<String, Object> row = new HashMap<>();
                                for (int i = 1; i <= columnCount; i++) {
                                    String columnName = metaData.getColumnName(i);
                                    Object columnValue = rs.getObject(i);
                                    row.put(columnName, columnValue);
                                }
                                results.add(row);
                                //支持查询数量
                            } else if (int.class.isAssignableFrom(resultType)|| Integer.class.isAssignableFrom(resultType)) {
                                // 特殊处理 int 类型
                                int columnValue = rs.getInt(1); // 假设返回值在第一列
                                results.add(columnValue);
                            } else if (String.class.isAssignableFrom(resultType)) {
                                // 特殊处理 String 类型
                                String columnValue = rs.getString(1); // 假设返回值在第一列
                                results.add(columnValue);
                            } else {
                                //返回指定类型
                                Object row = resultType.getDeclaredConstructor().newInstance();
                                for (int i = 1; i <= columnCount; i++) {
                                    String columnName = metaData.getColumnName(i);
                                    Object columnValue = rs.getObject(i);
                                    setFieldValue(row, columnName, columnValue);
                                }
                                results.add(row);
                            }

                        }
                    }
                    if (results.size() == 1 && (int.class.isAssignableFrom(resultType)||
                            Integer.class.isAssignableFrom(resultType) || String.class.isAssignableFrom(resultType))) {
                        // 直接返回单个 int 值
                        result = results.get(0);
                    } else {
                        result = results;
                    }
                } else {
                    pstmt.executeUpdate();
                    conn.commit();
                }
                logger.info("【开启数据双写】，执行sql={}",sql);
            }
        } catch (Exception ex) {
            // 处理异常，回滚事务
            if (conn != null && !conn.isClosed()) {
                try {
                    conn.rollback();
                } catch (SQLException rollbackEx) {
                    logger.error("事物处理异常，回滚失败！ex=[{}]", rollbackEx);
                }
            }
            // 处理异常
            // 这里可以记录错误日志或抛出异常
            ex.printStackTrace();
        }finally {
//            // 确保连接和 SqlSession 在所有操作完成后关闭
//            if (conn != null && !conn.isClosed()) {
//                conn.close();
//            }
            // 使用 ThreadLocal 释放 SqlSession
            releaseSqlSession();

        }
        return result;
    }


    /**
     * 获取结果集对象
     * @param mappedStatement
     * @return
     */
    private Class<?> getResultType(MappedStatement mappedStatement) {
        Type type = mappedStatement.getResultMaps().get(0).getType();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            return (Class<?>) parameterizedType.getActualTypeArguments()[0];
        } else {
            return (Class<?>) type;
        }
    }

    /**
     * 封装结果集对象
     * @param
     * @return
     */
    private void setFieldValue(Object obj, String fieldName, Object value) {
        try {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            if (field.getType() == Date.class && value instanceof LocalDateTime){
                field.set(obj, convertToLocalDate((LocalDateTime) value));
            } else {
                field.set(obj, value);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private Date parseDate(String dateString) throws ParseException {
        return DATE_FORMAT.parse(dateString);
    }


    public static Date convertToLocalDate(LocalDateTime localDateTime) {
        // 将 LocalDateTime 转换为 Instant
        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
        // 将 Instant 转换为 Date
        return Date.from(instant);
    }

    /**
     * 获取带参SQL
     *
     * @param configuration 节点的配置
     * @param boundSql      封装myBatis最终产生的sql类
     * @return
     */
    public static String getSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();//获取参数
        List<ParameterMapping> parameterMappings = boundSql
                .getParameterMappings();
        //sql语句中多个空格都用一个空格代替
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            //获取类型处理器注册器，类型处理器的功能是进行java类型和数据库类型的转换。如果根据parameterObject.getClass(）可以找到对应的类型，则替换
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
            } else {
                //MetaObject主要是封装了originalObject对象，提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?",  Matcher.quoteReplacement(getParameterValue(obj)));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        //该分支是动态sql
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    } else {
                        //打印出缺失，提醒该参数缺失并防止错位
                        sql = sql.replaceFirst("\\?", "缺失");
                    }
                }
            }
        }
        return sql;
    }


    /**
     * 对SQL进行格式化
     *
     * @param obj
     * @return
     */
    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(new Date()) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "null";
            }
        }
        return value;
    }


    private Method getMethod(String id,Class mapperClass,Object[] paramValues) throws NoSuchMethodException {
        //获取参数对应的class
        StringJoiner stringJoiner = new StringJoiner("_");
        String methodName = id.substring(id.lastIndexOf(".") + 1);
        stringJoiner.add(id);
        Class<?>[] classes = new Class[paramValues.length];
        for (int i = 0; i< paramValues.length;i++){
            classes[i] = paramValues[i].getClass();
            stringJoiner.add(classes[i].getSimpleName());
        }
        String key = stringJoiner.toString();
        Method method = executionMethod.get(key);
        if (method == null){
            synchronized (executionMethod){
                method = findMethodByMethodSignature(mapperClass, methodName, classes);
                if (method == null){
                    throw new NoSuchMethodException("No such method " + methodName + " in class " + mapperClass.getName());
                }
                executionMethod.put(key,method);
            }
        }
        return method;
    }

    private Method findMethodByMethodSignature(Class mapperClass,String methodName,Class<?>[] parameterTypes) throws NoSuchMethodException {
        //class参数为空 为无参数方法
        Method method = null;
        List<Method> finded = new ArrayList<Method>();
        for (Method m : mapperClass.getMethods()) {
            if (m.getName().equals(methodName)) {
                finded.add(m);
            }
        }
        if (parameterTypes.length == 0) {
            if (finded.isEmpty()) {
                throw new NoSuchMethodException("No such method " + methodName + " in class " + mapperClass.getName());
            }
            if (finded.size() > 1) {
                String msg = String.format("Not unique method for method name(%s) in class(%s), find %d methods.",
                        methodName, mapperClass.getName(), finded.size());
                throw new IllegalStateException(msg);
            }
            method= finded.get(0);
        }else{
            for (Method m : finded){
                int parameterCount = m.getParameterCount();
                if (parameterCount != parameterTypes.length) continue;
                Class<?>[] realParamTypes = m.getParameterTypes();
                for (int i = 0;i < realParamTypes.length;i++){
                    if (!realParamTypes[i].isAssignableFrom(parameterTypes[i])){
                        continue;
                    }
                }
                method = m;

            }
        }
        return method;
    }


    private Object[] getParamValue(Object parameter){
        List<Object> paramValues = new ArrayList<>();
        if (parameter != null && parameter instanceof MapperMethod.ParamMap){
            MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter;
            int count = 1;
            while (count <= paramMap.size() / 2){
                try {
                    paramValues.add(paramMap.get("param"+(count++)));
                }catch (BindingException e){
                    break;
                }
            }

        }else if (parameter != null){
            paramValues.add(parameter);
        }
        return paramValues.toArray();
    }


    public SqlSession getSqlSession() {
        SqlSession sqlSession = sqlSessionThreadLocal.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactoryWrapper.getSqlSessionFactory().openSession();
            sqlSessionThreadLocal.set(sqlSession);
        }
        return sqlSession;
    }



    public void releaseSqlSession() {
        SqlSession sqlSession = sqlSessionThreadLocal.get();
        if (sqlSession != null) {
            //sqlsession.close是会关闭conn连接信息
            sqlSession.close();
            sqlSessionThreadLocal.remove();
        }
    }


    protected abstract Object doExecute(Invocation invocation, TableConfiguration tableRule, MetaObject metaStatementHandler, MappedStatement mappedStatement) throws InvocationTargetException, IllegalAccessException;

}
